package switchviz.network.snmp;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.Target;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.DefaultPDUFactory;

import switchviz.core.data.OIDDataPair;
import switchviz.network.NetworkData;

public class SnmpAdaptor
{	
	private static Logger logger = Logger.getLogger(SnmpAdaptor.class);
	
	private CommunityTarget _target = null;
	private volatile Snmp _snmp = null;
	private SnmpStatus _status = new SnmpStatus();
	
	private int _pduCapacity = 0;
	private List<SnmpPdu> _pduList = new ArrayList<SnmpPdu>();
	
	/* c'tors ****************************************************************/
	public SnmpAdaptor(String address, int port, String community, long timeout, int maxRetries, int pduCapacity) throws IllegalArgumentException,
		IOException
	{
		_pduCapacity = pduCapacity;
		
		// initialize community target
		if (logger.isInfoEnabled())
			logger.info("Creating community target."
				+ "\n\t address=" + address + " port=" + port + " community=" + community);
			
		_target = new CommunityTarget(SnmpAdaptor.parseAddress(address, port), new OctetString(community));
		_target.setVersion(SnmpConstants.version2c);
		_target.setTimeout(timeout);
		_target.setRetries(maxRetries);
		
		// initialize snmp instance
		logger.info("Creating snmp communication.");
		
		_snmp = new Snmp(new DefaultUdpTransportMapping());
		_snmp.listen();
	}
	
	public SnmpStatus getSnmpStatus()
	{
		return _status;
	}
		
	/* properties ************************************************************/
	public void addGet(NetworkData data)
	{
		add(data, PDU.GET);
	}
	
	public void addGetNext(NetworkData data)
	{
		add(data, PDU.GETNEXT);
	}
	
	public void add(NetworkData data, int type)
	{
		if (_pduList.size() == 0 || 
				_pduList.get(_pduList.size() - 1).size() >= _pduCapacity)
			_pduList.add(new SnmpPdu(createPdu(_target, type)));
		
		SnmpPdu pdu = _pduList.get(_pduList.size() - 1);
		
		pdu.add(data);
	}
	
	
	public void send(boolean synchron) throws IOException
	{
		if (logger.isInfoEnabled())
			logger.info("Send " + _pduList.size() + " pdu's to target.");
		
		if (synchron)
			sendSynchron();
		else
			sendAsynchronous();
	}
	
	public synchronized void close() throws IOException
	{
		logger.info("Closing snmp communication.");
		for (SnmpPdu pdu : _pduList)
		{
			_snmp.cancel(pdu.getPdu(), pdu);
		}	

		_snmp.close();
	}
	
	private void sendAsynchronous() throws IOException
	{
		for (SnmpPdu pdu : _pduList)
		{
			_status.updateStatus(SnmpStatus.Status.Send);
			_snmp.send(pdu.getPdu(), _target, _status, pdu);
		}		
	}
	
	private void sendSynchron() throws IOException
	{
		for (SnmpPdu pdu : _pduList)
		{
			ResponseEvent responseEvent = _snmp.send(pdu.getPdu(), _target);
			
			pdu.onResponse(responseEvent);
		}		
	}

	protected static Address parseAddress(String address, int port) throws IllegalArgumentException
	{
		return parseAddress(address + "/" + port);
	}
	
	private static Address parseAddress(String address) throws IllegalArgumentException
	{
		return GenericAddress.parse(address);
	}
	
	private static PDU createPdu(Target target, int pduType)
	{
		return DefaultPDUFactory.createPDU(target, pduType);
	}
	
	public OIDDataPair[] getSynchronBulk(String oid, int limit) throws IOException
	{
		PDU pdu = new PDU();
		pdu.add(new VariableBinding(new OID(oid)));
		pdu.setType(PDU.GETBULK);
		pdu.setMaxRepetitions(limit);
		pdu.setNonRepeaters(0);
		ResponseEvent responseEvent = _snmp.getBulk(pdu, _target);
		PDU responsePDU = responseEvent.getResponse();
		OIDDataPair[] bulk = new OIDDataPair[responsePDU.size()];
		for(int i = 0; i < bulk.length; ++i)
		{
			bulk[i] = new OIDDataPair(SnmpPdu.processValue(responsePDU.get(i).getVariable()), responsePDU.get(i).getOid().toString());
		}
		return bulk;
	}
}